package org.tuio4j;

import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;

import org.tuio4j.message.TuioMessage;
import org.tuio4j.profile.cursor2d.Profile2DCursor;
import org.tuio4j.profile.obj2d.Profile2DObject;
import org.tuio4j.util.Flag;

import com.illposed.osc.OSCListener;
import com.illposed.osc.OSCMessage;
import com.illposed.osc.OSCPortIn;

/**
 * See license.txt for license information.
 * @author Uwe Laufs
 * @version 1.1
 */
public class TuioClient implements OSCListener, TUIOProfileListener {
	private int portNumber;
	private OSCPortIn osc;
	private Flag connectionFlag = new Flag();
	private ArrayList<TuioClientListener> listeners = new ArrayList<TuioClientListener>();
	private Hashtable<String, ArrayList<TuioProfile>> profileTable = new Hashtable<String, ArrayList<TuioProfile>>();
	

	/**
	 * Creates a new tuio client. 
	 * @param portNumber port number of the tuio data source.
	 * @param profiles the TUIO Profiles to be used.
	 */
	public TuioClient(int portNumber, TuioProfile[] profiles){
		this.portNumber = portNumber;
		for (int i = 0; i < profiles.length; i++) {
			this.registerProfile(profiles[i]);
		}
	}
	/**
	 * Creates a new tuio client. It is set to defualt port 3333.
	 * @param profiles The TUIO Profiles to be used.
	 */
	public TuioClient(TuioProfile[] profiles){
		this(3333, profiles);
	}

	/**
	 * Creates a new tuio client. Default TUIO Profiles are used (2D Cursors and 2D Objects).
	 * @param portNumber port number of the tuio data source.
	 */
	public TuioClient(int portNumber){
		this(portNumber, new TuioProfile[]{new Profile2DCursor(), new Profile2DObject()});
	}
	/**
	 * Creates a new tuio client. it uses default TUIO port number (3333). Default TUIO Profiles are used (2D Cursors and 2D Objects).
	 */
	public TuioClient(){
		this(3333);
	}
	/**
	 * connects the client. If the client is already connected, connect() call is ignored.
	 * @param portNumber UDP port on which the desired TUIO data source is running.
	 */
	public void connect(int portNumber){
		this.portNumber = portNumber;
	}
	/**
	 * connects the client. If the client is already connected, connect() call is ignored.
	 */
	public void connect() throws ConnectException {
		if(!this.isConnected()){
			try {
				osc = new OSCPortIn(portNumber);
				String[] availableProfileAddresses = this.profileTable.keySet().toArray(new String[this.profileTable.size()]);
				for (int i = 0; i < availableProfileAddresses.length; i++) {
					osc.addListener(availableProfileAddresses[i], this);
//					System.out.println("[TUIO4j]: listen to " + availableProfileAddresses[i]);
				}
				osc.startListening();
				connectionFlag.setValue(true);
			} catch (Throwable e) {
				connectionFlag.setValue(false);
				throw new ConnectException(e.getMessage());
			}
		}
	}
	/**
	 * Disconnects the client. The client can be reconnected by calling connect().
	 */
	public void disconnect() {
		osc.stopListening();
		try {
			Thread.sleep(100);
		} catch (Exception e) {
//			System.out.println(e);
		}
		osc.close();
		this.osc = null;
		connectionFlag.setValue(false);
	}
	/* (non-Javadoc)
	 * @see com.illposed.osc.OSCListener#acceptMessage(java.util.Date, com.illposed.osc.OSCMessage)
	 */
	@Override
	public void acceptMessage(Date time, OSCMessage m) {
		TuioMessage tm = new TuioMessage(m);
		ArrayList<TuioProfile> profiles = this.profileTable.get(tm.getMessageAdress());
		for (int i = 0; i < profiles.size(); i++) {
			profiles.get(i).consumeMessage(new TuioMessage(m));
		}
	}
	/* (non-Javadoc)
	 * @see org.tuio4j.TUIOProfileListener#eventProducedByProfile(org.tuio4j.TuioProfile, org.tuio4j.TuioEvent)
	 */
	@Override
	public void eventProducedByProfile(TuioProfile profile, TuioEvent event) {
		for (int i = 0; i < this.listeners.size(); i++) {
			this.listeners.get(i).eventReceived(event);
		}
//		System.out.println("event: " + event);
	}
	/**
	 * @return returns the port number to which the client listens.
	 */
	public int getPortNumber() {
		return portNumber;
	}
	/**
	 * @return True if client is connected, else false
	 */
	public boolean isConnected() {
		return connectionFlag.getValue();
	}
	/**
	 * Adds a TuioClientListener to the client.
	 * @param listener
	 */
	public void addListener(TuioClientListener listener){
		if(!this.listeners.contains(listener)){
			this.listeners.add(listener);
		}
	}
	
	/**
	 * @return true if one or more listener(s) are registered, else false
	 */
	public boolean hasListeners(){
		return this.listeners.size()>0;
	}
	
	/**
	 * Removes a registered client listener. If the listener is not registered, the method call is ignored.
	 * @param TuioClientListener The listener to be removed.
	 */
	public void removeListener(TuioClientListener listener){
		if(this.listeners.contains(listener)){
			this.listeners.remove(listener);
		}
	}
	/**
	 * Registers a TuioProfile to the client.
	 * @param profile TuioProfile to be registered.
	 */
	private void registerProfile(TuioProfile profile){
		String[] addresses = profile.getProfileAddress();
		for (int i = 0; i < addresses.length; i++) {
			ArrayList<TuioProfile> profiles = profileTable.get(addresses[i]);
			if(profiles == null){
				profiles = new ArrayList<TuioProfile>();
			}
			if(!profiles.contains(profile)){
				profiles.add(profile);
			}
			this.profileTable.put(addresses[i], profiles);
		}
		profile.addListener(this);
	}
}
